/*
    This file is part of Posixcpp library.

    Posixcpp library is free software: you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public License
    as published by the Free Software Foundation, either version 3 of
    the License, or (at your option) any later version.

    Posixcpp library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU LesserGeneral Public
    License along with Posixcpp library. If not, see
    <http://www.gnu.org/licenses/>.
*/

/**
 * @file thread.cpp
 * @brief Timespec class implementation
 */

#include "posix_time_timespec.hpp"

#include <stdexcept>
#include <iomanip>

#define SEC_mSEC 1000LL
#define SEC_uSEC (1000LL * SEC_mSEC)
#define SEC_nSEC (1000LL * SEC_uSEC)

using posix::time::Timespec;

const int64_t posix::time::SEC_2_mSEC = SEC_mSEC;
const int64_t posix::time::SEC_2_uSEC = SEC_uSEC;
const int64_t posix::time::SEC_2_nSEC = SEC_nSEC;

Timespec::Timespec( ) :
m_data( {0, 0} )
{
}

Timespec::Timespec( const timespec & data ) :
m_data( data )
{
}

Timespec::Timespec( const timeval & data )
{
	TIMEVAL_TO_TIMESPEC(&data, &m_data);
}

Timespec::Timespec( time_t sec, long nsec ) :
m_data({sec, nsec} )
{
}

Timespec::Timespec( int64_t nsec )
{
	m_data.tv_sec = nsec / SEC_nSEC;
	m_data.tv_nsec = nsec % SEC_nSEC;
}

const timespec &Timespec::data( ) const
{
	return m_data;
}

timespec &Timespec::data( )
{
	return m_data;
}

time_t Timespec::sec( ) const
{
	return m_data.tv_sec;
}

long Timespec::nsec( ) const
{
	return m_data.tv_nsec;
}

Timespec & Timespec::operator +=(const Timespec & other)
{
	m_data.tv_sec += other.m_data.tv_sec;

	unsigned long nsec = m_data.tv_nsec + other.m_data.tv_nsec;
	if (nsec < SEC_nSEC)
		m_data.tv_nsec = nsec;
	else
	{
		m_data.tv_sec += nsec / SEC_nSEC;
		m_data.tv_nsec = nsec % SEC_nSEC;
	}

	return *this;
}

Timespec & Timespec::operator -=(const Timespec & other)
{
	if (m_data.tv_sec >= other.m_data.tv_sec)
	{
		m_data.tv_sec -= other.m_data.tv_sec;
		if (m_data.tv_nsec >= other.m_data.tv_nsec)
			m_data.tv_nsec -= other.m_data.tv_nsec;
		else
		{
			if ((--m_data.tv_sec) >= 0)
				m_data.tv_nsec = m_data.tv_nsec + SEC_nSEC - other.m_data.tv_nsec;
		}
	}
	else
		throw std::runtime_error("Negative values not supported yet");
    return *this;
}

Timespec Timespec::operator -(const Timespec& other) const
{
	Timespec res = *this;
	res -= other;
	return res;
}

Timespec Timespec::operator +(const Timespec& other) const
{
	Timespec res = *this;
	res += other;
	return res;
}

Timespec::operator int64_t( )
{
	return m_data.tv_sec * SEC_nSEC + m_data.tv_nsec;
}

namespace posix
{
namespace time
{

std::ostream & operator <<(std::ostream & stream, const Timespec & item)
{
        stream << std::setw(4) << std::setfill('0') << item.m_data.tv_sec << ':'
           << std::setw(9) << std::setfill('0') << item.m_data.tv_nsec;

    return stream;
}

}
}
